#include "StdAfx.h"

#include "Application.h"
#include "Window/Window.h"
#include "Graphics/GraphicsLayer.h"
#include "input/InputLayer.h"

#include "../nvwa/debug_new.h"
#include <GL/glut.h>

#include <stdlib.h>
#include <stdarg.h>

/* OpenGL and friends */
#ifdef USEGLEW
#include <GL/glew.h>
#endif
#define GL_GLEXT_PROTOTYPES
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#define GLIBCPP_FORCE_NEW = 1;

using namespace std;

cApplication* cApplication::m_pGlobalApp = NULL;

/**
 * Se abstrae la creacion de la ventana que se repite en todos los programas
 */
int main(int argc, char* argv[])
{

    glutInit(&argc, argv);
    cApplication* pApp;

    try {
        pApp = CreateApplication();

        pApp->Init();
        pApp->SceneInit();
        pApp->Run();
    } catch (int err) {
        delete pApp;
        return 0;
    }

    delete pApp;
    return 0;
}

cApplication::cApplication()
{
    if (m_pGlobalApp) {
        std::cout << "Ya existe una instancia de Applicacion\n";
    }

    m_pGlobalApp = this;

    //Setear variables de la ventana
    this->m_title = "Mi aplicacion grafica";
    this->m_width = cApplication::APP_WIDTH;
    this->m_height = cApplication::APP_HEIGHT;
    this->m_red = cApplication::APP_RED;
    this->m_green = cApplication::APP_GREEN;
    this->m_blue = cApplication::APP_BLUE;
    this->m_alpha = cApplication::APP_ALPHA;
    this->m_bpp = cApplication::APP_DEPTH;
    this->m_snl = cApplication::APP_STENCIL;
    this->m_mode = cApplication::APP_MODE;
    this->m_bActive = true;
    this->m_dAppShowFps = true;
    
    GetFrameworkPaths();
}

cApplication::~cApplication()
{
    m_pGlobalApp->m_dAppMedia.clear();
    m_pGlobalApp->m_dAppRoot.clear();
    m_pGlobalApp->m_dAppTextures.clear();
    m_pGlobalApp->m_dFrameworkFile.clear();
    m_pGlobalApp->m_dFrameworkGraphics.clear();
    m_pGlobalApp->m_dFrameworkRoot.clear();
    m_pGlobalApp->m_dFrameworkShaders.clear();
    m_pGlobalApp->m_dFrameworkWindow.clear();

    m_pGlobalApp = NULL;

    //delete Sound();
    delete MainWindow();
    delete Graphics();
    delete InputLayer();
}

void cApplication::Init()
{
    this->InitPrimaryWindow();
    this->InitGraphics();
    this->InitInput();
    this->InitSound();
    this->InitExtraSubsystems();
}

void cApplication::GetFrameworkPaths()
{
    char cCurrentPath[FILENAME_MAX];

    if (!getcwd(cCurrentPath, sizeof (cCurrentPath))) {
        std::cout << "No se encontro el directorio de trabajo actual";
        exit(EXIT_FAILURE);
    }

    cCurrentPath[sizeof (cCurrentPath) - 1] = '\0'; /* not really required */
    strcat(cCurrentPath, "/");

    this->m_dAppRoot.append(cCurrentPath);
    this->m_dAppMedia.append(cCurrentPath);
    this->m_dAppTextures.append(cCurrentPath);
    this->m_dFrameworkRoot.append(cCurrentPath);
    this->m_dFrameworkFile.append(cCurrentPath);
    this->m_dFrameworkGraphics.append(cCurrentPath);
    this->m_dFrameworkShaders.append(cCurrentPath);
    this->m_dFrameworkWindow.append(cCurrentPath);

    this->m_dAppRoot.append("3DApp/");
    this->m_dAppMedia.append("3DApp/Media/");
    this->m_dAppTextures.append("3DApp/Media/Textures/");
    this->m_dFrameworkRoot.append("Framework/");
    this->m_dFrameworkFile.append("Framework/file/");
    this->m_dFrameworkGraphics.append("Framework/Graphics/");
    this->m_dFrameworkShaders.append("Framework/shaders/");
    this->m_dFrameworkWindow.append("Framework/window/");
}

inline const char* bool_cast(const bool b)
{
    return b ? "true" : "false";
}

void cApplication::Run()
{
    // Constante booleana para establecer que la ventana está corriendo
    int running = GL_TRUE;
    double frameCount = 0;
    int fps = 0;
    float timeInterval = 0.0;
    float delta = 0.0;
    double lastTime = glfwGetTime();

    while (running) {
        
        cInputLayer::GetInputLayer()->UpdateDevices();
        
        //  Increase frame count
        frameCount++;
        
        /**
         * Calcula el delta de tiempo y renderiza el frame.
         */
        double currTime = glfwGetTime();
        //  Calculate time passed
        timeInterval = currTime - lastTime;
        delta = timeInterval;
        
        if(timeInterval > 1) {
            // Calculate the number of frames per second
            fps = (frameCount / delta);
            // Set time
            lastTime = currTime;
            //  Reset frame count
            frameCount = 0;
        }
        
        if (this->m_bActive) {
            this->Update(delta);
            this->DoFrame(delta);
        } else {
            this->DoIdleFrame(delta);
        }
        
        if(this->m_dAppShowFps) {
            std::string sFPS;
            std::stringstream out;
            out << fps;
            sFPS = "fps: " + out.str();
            this->printAt(0, 0, &sFPS[0]);
        }

        // Analizar si ESC fue presionado o la tecla de cerrado
        running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);

        // Intercambiar los buffers (front y back)
        glfwSwapBuffers();
    }

    // Se cierra la ventana y se finaliza GLFWprueba2
    glfwTerminate();
}

double cApplication::calcularFPS(double theTimeInterval, bool log) {
    // Los valores estáticos que unicamente son inicializados la primera vez que corre la función
    static double t0Value = glfwGetTime(); // Inicializar el tiempo con (ahora)
    static int fpsFrameCount = 0; // Inicializar el contador de FPS en 0
    static double fps = 0.0; // Inicializar el valor de FPS en 0.0

    // Tomar el valor actual del tiempo en segundos (no estático, ejecutado cada vez que la función es llamada)
    double currentTime = glfwGetTime();

    // Asegurarnos que el tiempo del intervalo entre FPS es sano (mínimo = 0.1s, máximo = 10.0s)
    // Los números negativos son inválidos
    if (theTimeInterval < 0.1) {
        theTimeInterval = 0.1;
    }
    if (theTimeInterval > 10.0) {
        theTimeInterval = 10.0;
    }

    std::cout << "currentTime: " << currentTime << std::endl;
    std::cout << "t0Value: " << t0Value << std::endl;
    std::cout << "theTimeInterval: " << theTimeInterval << std::endl;
    // Calcular los FPS en el intervalo especificado
    if ((currentTime - t0Value) > theTimeInterval) {
        // Calcular FPS => como el número de frames dividido por el intervalo en segundos
        fps = (double) fpsFrameCount / (currentTime - t0Value);

        // Si el usuario desea loguear
        if (log) {
            std::cout << "FPS: " << fps << std::endl;
        }

        // Resetear el contador de FPS y setear el tiempo original como AHORA
        fpsFrameCount = 0;
        t0Value = glfwGetTime();
    } 
    // Si aún no se alcanzó el intervalo se debe incrementar el contador de fps
    else 
    {
        std::cout << "else" << std::endl;
        fpsFrameCount++;
    }
    
    return fps;
}

void cApplication::Update(float timeDelta) {}

void cApplication::DoFrame(float timeDelta) {}

void cApplication::DoIdleFrame(float timeDelta) {}

void cApplication::SceneInit() {
    // por default no hay Scene para iniciar
}

void cApplication::InitPrimaryWindow()
{
    // Se inicializa GLEW
    glewInit();
    
    new cWindow(this->m_title, this->m_mode, this->m_width, this->m_height, this->m_red, this->m_green, this->m_blue, this->m_alpha, this->m_bpp, this->m_snl);

    MainWindow()->InitInstance();
}

void cApplication::InitGraphics() {
    cGraphicsLayer::Create();
}

void cApplication::InitInput() {
    cInputLayer::Create(true, true);
}

void cApplication::InitSound() {}

void cApplication::InitExtraSubsystems() {}

void cApplication::SceneEnd() {}

// ****************** PRINT CHAR IN SCREEN ******************

void cApplication::printAt(int x, int y, const char* format, ...)
{
  va_list args;
  glWindowPos2i(x,y);
  va_start(args,format);
  char buf[LEN];
  char* ch=buf;
  vsnprintf(buf,LEN,format,args);
  while (*ch)
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,*ch++);
  va_end(args);
}

// ****************** PRINT CHAR IN SCREEN ******************